#include "evconnsvrd.h"
#include "libev_wrapper.h"
#include "version.h"
#include "cfg.h"
#include "callback.h"
#include "conn_event.h"
#include "process.h"

struct ConnCtx* gCtx;

void usage()
{
	fprintf(stderr, "evconnsvrd(connection server implemented by libevent) usage:\n"
		"\t--conf|-C  [../conf/conf.xml]\n"
		"\t--version|-V\n"
		"\t start|stop|reload\n");
}

int32_t evconn_ctx_init(const char* cfg_file)
{
	/* context */
	gCtx = (struct ConnCtx*)MALLOC(sizeof(struct ConnCtx));
	gCtx->cfg = (struct ConnCfg*)MALLOC(sizeof(struct ConnCfg));
	gCtx->ev_address = 0;
	gCtx->reload_flag = 0;
	gCtx->stop_flag = 0;
	gCtx->connected_conn_count = 0;
	gCtx->waiting_conn_count = 0;
	
	snprintf(gCtx->cfg->cfg_file, sizeof(gCtx->cfg->cfg_file), "%s", cfg_file);
	gettimeofday(&gCtx->start, NULL);

	/* load cfg */
	if(load_cfg(gCtx, cfg_file, 0) < 0)
	{
		FREE(gCtx->cfg);
		FREE(gCtx);
		return -1;
	}
	return 0;
}

void evconn_stop(const char* cfg_file)
{
	/* context */
	if(evconn_ctx_init(cfg_file) < 0)
		goto FINI_STOP;

	/* pid file */
	if(process_exist(gCtx->cfg->pid_file, "evconnsvrd") < 0)
	{
		fprintf(stderr, "evconnsvrd not running\n");
		goto FINI_STOP;
	}

	/* send stop signal to evconnsvrd */
	int32_t pid = read_pid_from_file(gCtx->cfg->pid_file);
	kill(pid, EVCONN_STOP_SIGNAL);
	fprintf(stderr, "send stop signal to evconnsvrd[%d] success\n", pid);

FINI_STOP:
	FREE(gCtx->cfg);
	FREE(gCtx);
	exit(0);
}

void evconn_reload(const char* cfg_file)
{
	/* context */
	if(evconn_ctx_init(cfg_file) < 0)
		goto FINI_RELOAD;

	/* pid file */
	if(process_exist(gCtx->cfg->pid_file, "evconnsvrd") < 0)
	{
		fprintf(stderr, "evconnsvrd not running\n");
		goto FINI_RELOAD;
	}

	/* send stop signal to evconnsvrd */
	int32_t pid = read_pid_from_file(gCtx->cfg->pid_file);
	kill(pid, EVCONN_RELOAD_SIGNAL);
	fprintf(stderr, "send reload signal to evconnsvrd[%d] success\n", pid);

FINI_RELOAD:
	FREE(gCtx->cfg);
	FREE(gCtx);
	exit(0);
}

void evconn_init(const char* cfg_file)
{
	/* context */
	if(evconn_ctx_init(cfg_file) < 0)
	{
		FREE(gCtx->cfg);
		FREE(gCtx);
		exit(0);
	}

	/* pid file */
	if(0 == process_exist(gCtx->cfg->pid_file, "evconnsvrd"))
	{
		fprintf(stderr, "evconnsvrd is already running\n");
		FREE(gCtx->cfg);
		FREE(gCtx);
		exit(0);
	}

	/* daemon, don't keep fd */
	daemonize(0);

	/* write pid */
	write_pid_to_file(gCtx->cfg->pid_file);

	/* waiting list */
	INIT_LIST_HEAD(&gCtx->waiting_list);

	/* buddy allocator */
	gCtx->buddy = buddy_init(gCtx->cfg->mm_max_size, 
		gCtx->cfg->mm_unit_size);
	assert(gCtx->buddy);

	/* log */
	gCtx->log = log_init(gCtx->cfg->log_type, 
		gCtx->cfg->log_level, 
		gCtx->cfg->log_name,
		gCtx->cfg->log_args);
	assert(gCtx->log);

	/* lsi */
	gCtx->lsi = lsi_init(gCtx->cfg->lsi_id, gCtx->cfg->lsi_ip,gCtx->cfg->lsi_version);
	assert(gCtx->lsi);
	
	/* init libevent */
	int ret = libev_init();
	assert(0 == ret);
	gCtx->reactor = libev_init_base();
	assert(gCtx->reactor);

	/* add stop event */
	gCtx->stop_event = event_new(gCtx->reactor, 
		EVCONN_STOP_SIGNAL,
		EV_SIGNAL | EV_PERSIST,
		evconn_stop_cb, NULL);
	assert(gCtx->stop_event);
	event_add(gCtx->stop_event, NULL);

	/* add reload event */
	gCtx->reload_event = event_new(gCtx->reactor, 
		EVCONN_RELOAD_SIGNAL,
		EV_SIGNAL | EV_PERSIST,
		evconn_reload_cb, NULL);
	assert(gCtx->reload_event);
	event_add(gCtx->reload_event, NULL);

	/* add tick timer */
	gCtx->timer = event_new(gCtx->reactor, -1, EV_PERSIST, evconn_tick_cb, NULL);
	assert(gCtx->timer);
	struct timeval timer_tv;
	timer_tv.tv_sec = gCtx->cfg->tick_interval;
	timer_tv.tv_usec = 0;
	event_add(gCtx->timer, &timer_tv);

	/* listener init */
	gCtx->listener = evconnlistener_new_bind(gCtx->reactor, 
		evconn_listener_cb, NULL,
		LEV_OPT_CLOSE_ON_EXEC |LEV_OPT_REUSEABLE,
		-1, &gCtx->cfg->listen_addr, gCtx->cfg->sock_len);
	assert(gCtx->listener);
	evconnlistener_set_error_cb(gCtx->listener, evconn_listener_error_cb);
	evconnlistener_enable(gCtx->listener);
	LOG_DEBUG("listener inited success, listen address:%s, fd=%d",
		gCtx->cfg->listen_addr_str, evconnlistener_get_fd(gCtx->listener));

	/* conn-event pool init */
	gCtx->connev_pool = connev_pool_init(gCtx->cfg->conn_buffer_size,
		gCtx->cfg->conn_pool_threshold);
	assert(gCtx->connev_pool);

	/* conn hash table init */
	gCtx->connev_table = hash_init(connev_hash, connev_cmp, 
		libev_malloc, libev_free, gCtx->cfg->conn_pool_threshold);
	assert(gCtx->connev_table);

	/* start evconnsvrd */
	LOG_DEBUG("evconnsvrd start.");
}

void evconn_run()
{
	while(1)
	{
		if(gCtx->stop_flag)
			break;

		if(gCtx->reload_flag)
		{
			/* only do reload */
			load_cfg(gCtx, gCtx->cfg->cfg_file, 1);
			log_set_level(gCtx->log, gCtx->cfg->log_level);
			gCtx->reload_flag = 0;
			LOG_DEBUG("reload complete.");	
		}

		gCtx->wait_flag = 1;
		event_base_loop(gCtx->reactor, EVLOOP_NONBLOCK);
		
		int lsi_ret = lsi_process();
		assert(lsi_ret >= 0);

		if((gCtx->wait_flag > 0) && (lsi_ret > 0))
			usleep(10);
	}
}

void evconn_release()
{
	/* finish evconnsvrd */
	LOG_DEBUG("evconnsvrd finish.");

	/* release conn-event hash table */
	hash_release(gCtx->connev_table);
	gCtx->connev_table = 0;

	/* release connection-pool */
	connev_pool_release(gCtx->connev_pool);
	gCtx->connev_pool = 0;

	/* remove listener */
	evconnlistener_disable(gCtx->listener);
	evconnlistener_free(gCtx->listener);
	gCtx->listener = 0;

	/* remove tick timer */
	event_del(gCtx->timer);
	event_free(gCtx->timer);
	gCtx->timer = 0;

	/* remove stop and reload event */
	event_del(gCtx->reload_event);
	event_free(gCtx->reload_event);
	gCtx->reload_event = 0;
	event_del(gCtx->stop_event);
	event_free(gCtx->stop_event);
	gCtx->stop_event = 0;
	
	/* free libevent */
	libev_free_base(gCtx->reactor);
	gCtx->reactor = 0;

	/* free lsi */
	lsi_release(gCtx->lsi);
	gCtx->lsi = 0;

	/* free log */
	log_release(gCtx->log);
	gCtx->log = 0;

	/* free buddy */
	buddy_release(gCtx->buddy);
	gCtx->buddy = 0;

	/* free context */
	FREE(gCtx->cfg);
	FREE(gCtx);
	gCtx = NULL;
}

int32_t main(int argc, char** argv)
{
	static struct option long_options[] = 
	{
		{"conf",		1,	NULL,	'C'},
		{"version",		0,	NULL,	'V'},
		{NULL,		0,	NULL,	0},
	};
	char c;
	char cfg_file[64];
	while((c = getopt_long(argc, argv, "C:V", long_options, NULL)) != -1)
	{
		switch(c)
		{
			case 'C':
				snprintf(cfg_file, sizeof(cfg_file), "%s", optarg);
				break;
			case 'V':
				fprintf(stderr, "%s", evconn_version_string());
				exit(0);
			default:
				usage();
				exit(0);
		}
	}

	if (optind >= argc)
	{
		usage();
		exit(0);
	}
	int32_t opt;
	for (opt = optind; opt < argc; opt++)
	{
		/* start evconnsvrd */
		if (0 == strcasecmp(argv[opt], "start"))
		{
			evconn_init(cfg_file);

			evconn_run();
			
			evconn_release();
		}
		
		else if(0 == strcasecmp(argv[opt], "stop"))
		{
			evconn_stop(cfg_file);
		}
		
		else if(0 == strcasecmp(argv[opt], "reload"))
		{
			evconn_reload(cfg_file);
		}
	}
	
	return 0;
}
